Skip to content

fix(integrations): escape multiline/control chars in SKILL.md frontmatter#3392

Open
Quratulain-bilal wants to merge 1 commit into
github:mainfrom
Quratulain-bilal:fix/skill-frontmatter-yaml-escape
Open

fix(integrations): escape multiline/control chars in SKILL.md frontmatter#3392
Quratulain-bilal wants to merge 1 commit into
github:mainfrom
Quratulain-bilal:fix/skill-frontmatter-yaml-escape

Conversation

@Quratulain-bilal

@Quratulain-bilal Quratulain-bilal commented Jul 7, 2026

Copy link
Copy Markdown
Contributor

fixes #3391

the skills setup path builds SKILL.md frontmatter by hand and its _quote helper only escaped backslash and quote. a double-quoted yaml scalar can't carry a raw newline or control character, so a multiline (block-scalar) description reparsed with its newlines collapsed to spaces, and a control character broke yaml loading entirely. the description comes from the template frontmatter, so any command with a block-scalar description hits the multiline corruption.

this is the SKILL.md sibling of the toml escaping in #3341 and the goose yaml issue #3382 — a different code path in the same file, plus the identical _quote twin in the hermes integration.

fix: add a shared specify_cli/_yaml_string.quote_yaml_double helper that emits a valid double-quoted scalar (newline/cr/tab short escapes, other control chars as \xXX) and route both renderers through it, so there's one implementation to keep correct.

added a regression test that round-trips a multiline description through the yaml parser. i confirmed it fails on the pre-fix code by stashing the source and re-running (first line\nsecond line -> 'first line second line '); it passes with the fix, and the skills suites stay green.

…tter

the hand-built SKILL.md frontmatter in the skills setup path (base.py, and
the hermes twin) wrapped values in a double-quoted yaml scalar but only
escaped backslash and quote. a multiline (block-scalar) description was then
emitted with raw newlines inside the quoted scalar and reparsed with those
newlines collapsed to spaces; a control character would break yaml loading
outright. the description comes from the template frontmatter, so this is
reachable for any command with a block-scalar description.

add a shared specify_cli/_yaml_string.quote_yaml_double helper that emits a
valid double-quoted scalar (newline/cr/tab short escapes, other control chars
as \xXX) and route both renderers through it. added a regression test that
round-trips a multiline description through the yaml parser.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Fixes YAML frontmatter generation for skills-based integrations by centralizing proper YAML double-quoted scalar escaping, preventing multiline descriptions and control characters from corrupting or breaking generated SKILL.md files.

Changes:

  • Added specify_cli/_yaml_string.quote_yaml_double() to escape newlines/CR/tab and other control characters into valid YAML escapes.
  • Switched the manual SKILL.md frontmatter renderers in SkillsIntegration and HermesIntegration to use the shared quoting helper.
  • Added a regression test to ensure multiline template descriptions round-trip through YAML parsing.
Show a summary per file
File Description
tests/integrations/test_integration_base_skills.py Adds regression coverage for multiline description round-trip in generated SKILL.md frontmatter.
src/specify_cli/integrations/hermes/init.py Uses shared YAML quoting helper for Hermes skill frontmatter rendering.
src/specify_cli/integrations/base.py Uses shared YAML quoting helper for skills frontmatter rendering in the base skills setup path.
src/specify_cli/_yaml_string.py Introduces a shared, reusable YAML double-quoted scalar escaping helper.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 4/4 changed files
  • Comments generated: 1
  • Review effort level: Low

Comment on lines +144 to +176
def test_skill_frontmatter_preserves_multiline_description(
self, tmp_path, monkeypatch
):
"""A multiline (block-scalar) description must round-trip exactly.

The hand-built SKILL.md frontmatter used to only escape backslash and
quote, so a block-scalar description was emitted with raw newlines inside
a double-quoted scalar and reparsed with those newlines collapsed to
spaces. The description must survive byte-for-byte."""
i = get_integration(self.KEY)
template = tmp_path / "sample.md"
template.write_text(
"---\n"
"description: |\n"
" first line\n"
" second line\n"
"scripts:\n"
" sh: scripts/bash/x.sh\n"
"---\n"
"Body\n",
encoding="utf-8",
)
monkeypatch.setattr(i, "list_command_templates", lambda: [template])

m = IntegrationManifest(self.KEY, tmp_path)
created = i.setup(tmp_path, m)
skill_files = [f for f in created if f.name == "SKILL.md"]
assert len(skill_files) == 1

content = skill_files[0].read_text(encoding="utf-8")
fm = yaml.safe_load(content.split("---", 2)[1])
assert "\n" in fm["description"]
assert fm["description"] == "first line\nsecond line\n"

@mnriem mnriem left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please address Copilot feedback and resolve conflicts

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

SKILL.md frontmatter corrupts multiline descriptions and breaks on control characters

3 participants